from django.shortcuts import render

# Create your views here.
from django.views import View

"""
请求传递数据的 4种形式
    提取URL的特定部分，如/weather/beijing/2018，可以在服务器端的路由中用正则表达式截取；
    查询字符串（query string)，形如key1=value1&key2=value2；
    请求体（body）中发送的数据，比如表单数据、json、xml；
    在http报文的头（header）中。


需求(做什么):     判断用户名是否重复

前端(请求):     行为描述
          当用户把用户名输入完成之后,光标移开的时候.前端应该获取用户输入的用户名,
          并通过 ajax 将用户名发送给后台服务器
        
后端:
        接收请求 (因为前端页面已经写好了,所以我们上课的时候 都是按照课件中 固定的形式来) 
                GET     usernames/xxxxx/count/   
        处理业务逻辑
                根据用户名查询这个用户名的用户数量
                如果数量是0 表示没有注册
                如果数量是1 表示注册了
        返回响应  
                JSON
                {count: 1/0}
"""
from apps.users.models import User
from django.http import JsonResponse


class UsernameCountView(View):
    def get(self, request, username):
        # if not re.match('',username):
        #     pass
        count = User.objects.filter(username=username).count()
        return JsonResponse({'count': count})


"""
需求:                实现注册功能[ 暂时不用实现 图片验证码和短信验证码]


前端(请求):         当用户把 用户名,密码,确认密码,手机号,图片验证码,短信验证码和是否同意协议 填写完成之后.点击注册按钮
                    会触发事件. 前端肯定要收集 用户输入的 信息. 发送 axios 请求. 以JSON形式的传递过来
                    POST    

后端(实现功能):
                请求
                        POST            /register/          参数在请求body中
                业务逻辑
                        1. 接收参数
                        2. 提取参数
                        3. 验证参数 [我们不相信前端提交的任何数据]
                        4. 数据入库
                        5. 返回响应
                响应
                        响应 code 0 跳转到首页
                            code 400 错误
"""
import json
import re


class RegisterView(View):
    def post(self, request):
        # 1. 接收参数
        # 1.1 获取 bytes数据
        body_bytes = request.body
        # 1.2 bytes转str
        body_str = body_bytes.decode()
        # 1.3 str转字典
        data = json.loads(body_str)

        # 2. 提取参数
        # 用户名,密码,确认密码,手机号和同意协议. 图片验证码和短信验证码 后期实现
        username = data.get('username')
        password = data.get('password')
        password2 = data.get('password2')
        mobile = data.get('mobile')
        allow = data.get('allow')

        # 3. 验证参数 [我们不相信前端提交的任何数据]
        # 3.1 这5个参数都要有数据
        # all[变量名,变量名,...] 只要不是None或者False  就返回True
        #                      只要有一个为None,或者False 就返回False
        if not all([username, password, password2, mobile, allow]):
            return JsonResponse({'code': 400, 'errmsg': '参数不全'})
        # 3.2 用户名需要符合规则 用户名也不能重复[不重复写了]
        if not re.match('[0-9A-Za-z_]{5,20}', username):
            return JsonResponse({'code': 400, 'errmsg': '用户名不符合规则'})
        # 3.3 密码符合规则
        if not re.match('[0-9a-zA-Z_]{8,20}', password):
            return JsonResponse({'code': 400, 'errmsg': '密码不符合规则'})
        # 3.4 密码和确认密码一致
        if password != password2:
            return JsonResponse({'code': 400, 'errmsg': '密码不一致'})
        # 3.5 手机号需要符合规则 手机号也不能重复[作业]
        if not re.match('1[3-9]\d{9}', mobile):
            return JsonResponse({'code': 400, 'errmsg': '手机号不符合规则'})
        # 3.6 得同意协议
        # 一会单独讲

        # 4. 数据入库
        # User().save()
        # User.objects.create()
        # 以上这2种方式 都是可以的. 但是会有一个问题
        # 密码没有加密

        # 要对密码进行加密
        # Django自带的用户模型中 有对密码加密的方法
        # create_user

        user = User.objects.create_user(
            username=username,
            password=password,
            mobile=mobile
        )

        from django.contrib.auth import login
        # 设置状态保持
        # login(请求对象,User实例对象)
        login(request, user)

        # 5.返回响应
        return JsonResponse({'code': 0, 'errmsg': 'ok'})


"""
需求:         实现登录


前端:     描述用户行为.用户行为结束之后,肯定要获取一些信息.这些信息就需要以请求的形式传递给服务器
        
        当用户 把 用户名和密码 输入完成之后,会点击登录按钮.
        点击登录按钮的话 肯定会发送axios请求. 发送请求的时候 肯定要携带 信息----用户名和密码 是否勾选记录登录

后端:
        请求:POST       username,password       JSON
        
        业务逻辑:
                    1.接收参数
                    2.提取参数
                    3.验证参数
                    4.判断用户名和密码是否正确
                    5.状态保持 -- session 服务器[相对安全]
                    6. 判断是否需要  n天免登录
                    7.返回响应
        响应:
                    JSON  {code:0}
"""

from django.contrib.auth import login


class LoginView(View):
    def post(self, request):
        # 1.接受参数
        data = json.loads(request.body.decode())
        # 2.提取参数
        username = data.get('username')
        password = data.get('password')
        remembered = data.get('remembered')
        # 3.验证参数
        if not re.match('[A-Za-z0-9_]{5,20}', username):
            return JsonResponse({'code': 0, 'errmsg': '用户名不符合规则'})

        # 4.判断用户名和密码是否正确
        # (1) 方式1
        # try:
        #     user = User.objects.get(username=username)
        # except User.DoesNotExist:
        #     return JsonResponse({'code': 400, 'errmsg': '用户名或密码错误'})
        # else:
        #     if not user.check_password(password):
        #         return JsonResponse({'code': 400, 'errmsg': '用户名或密码错误'})

        if re.match('1[3-9]\d{9}', username):
            User.USERNAME_FIELD = 'mobile'
        else:
            User.USERNAME_FIELD = 'username'

        # (2) 方式2
        # django为我们提供了 验证 账号和密码的方法
        from django.contrib.auth import authenticate
        # authenticate 会自动的验证 我们的 账号和密码
        # 如果账号和密码 正确 返回 user对象
        # 如果账号和密码 不正确 返回 None
        user = authenticate(username=username, password=password)

        if username is None:
            return JsonResponse({'code': 400, 'errmsg': '用户名或密码错误'})
        # 5.状态保持 -- session 服务器[相对安全]
        login(request, user)

        # 7.返回响应
        response = JsonResponse({'code': 0, 'errmsg': 'ok'})
        # 设置cookie

        # 6.判断是否需要 n天免登录
        if remembered:
            request.session.set_expiry(None)
            response.set_cookie('username', username, max_age=14 * 24 * 3600)
        else:
            request.session.set_expiry(0)
            response.set_cookie('username', user.username)

        return response


"""
需求:
            退出登录

前端:
            当用户点击退出按钮的时候,前端应该发送一个axios请求.  请求中会携带cookie
            
                
后端服务器:
        
            请求:         DELETE      logout/
            
            业务逻辑:
                        清除状态保持信息
            响应:
                    {code:0}
"""


class LogoutView(View):
    def delete(self, request):
        # 方式1
        # request.session.clear()
        # request.session.flush()

        # 方式2
        from django.contrib.auth import logout
        logout(request)

        response = JsonResponse({'code': 0, 'errmsg': 'ok'})

        response.delete_cookie('username')
        return response


"""
需求:         用户要访问个人中心页面

前端:
            用户点击个人中心.  前端要发送一个 axios请求.这个请求会携带cookie信息. cookie信息中有 sessionid

服务器:
        请求
                        GET         info/
        业务逻辑
                    1. 获取登录用户的信息
                    2. 组织为JSON数据
                    3. 返回响应
        响应
                    {
                        'code': 0, 
                        'errmsg': '个人中心',
                         "info_data":{
                                "username":"itcast",
                                "mobile": "18310820688",
                                "email": "",
                                "email_active": 'true'
                            }
                    }
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from utils.user import LoginRequiredJSONMixin


class UserCenterView(LoginRequiredJSONMixin, View):
    def get(self, request):
        # 1. 获取登录用户的信息
        # TODO request.user 是系统给我们添加的
        # TODO request.user 是根据 中间件 系统判断 请求信息
        # TODO 如果是登录用户 则获取当前登录用户的 user实例对象           User
        # TODO 如果不是登录用户 则获取的request.user 是一个匿名用户
        user = request.user

        # TODO 2.组织为JSON数据
        info_data = {
            'username': user.username,
            'mobile': user.mobile,
            'email': user.email,
            'email_active': user.email_active
        }
        # TODO 3.返回响应
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'info_data': info_data})


"""
需求:    保存用户的邮件信息

前端:    当用户输入完成邮件信息后,点击保存按钮
        会发送ajax[axios]请求,请求携带 邮箱信息
        PUT 和 POST和像  请求信息也是携带在 body里

后端:    请求
                    PUT     email/      JSON        body
        业务逻辑    
                    必须是登录用户
                    1. 获取请求数据
                    2. 提取参数
                    3. 验证参数
                    4. 更新数据
                    5. 返回响应
        响应
                code:0
"""
from utils.user import LoginRequiredJSONMixin


class EmailView(LoginRequiredJSONMixin, View):
    def put(self, request):
        # TODO 必须是登录用户

        # TODO 1. 获取请求数据
        data = json.loads(request.body.decode())
        # TODO 2. 提取参数
        email = data.get('email')
        # TODO 3. 验证参数
        if not re.match('[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}', email):
            return JsonResponse({'code': 400, 'errmsg': '邮箱格式不正确'})
        # TODO 4. 更新数据
        request.user.email = email
        request.user.save()

        # # 缺少的一个步骤 就是发送邮件!!!
        # from django.core.mail import send_mail
        # # subject, message, from_email, recipient_list,
        #
        # # subject,      主题
        # subject = '美多商城激活邮件'
        # # message,      消息 内容
        # message = ''  # 如果使用 html_message message为一个空字符串就可以
        # # from_email,   发件人
        # from_email = '美多商城官方邮箱<liu_dian_dian@163.com>'
        #
        # # recipient_list,  收件人列表
        # recipient_list = [email]
        #
        # html_message = '<a href="#">恭喜您,您中了由本公司特约提供的幻影一辆,请带1000W来本公司提车</a>'
        #
        # send_mail(subject,
        #           message,
        #           from_email,
        #           recipient_list,
        #           html_message=html_message)

        from apps.users.common import generic_email_token
        token = generic_email_token(email, request.user.id)

        # TODO url 的前边是固定的
        verify_url = 'http://www.meiduo.site:8080/success_verify_email.html?token=%s' % token

        from celery_meiduo.email.tasks import celery_send_email
        celery_send_email.delay(email, verify_url)

        # TODO 5.返回响应
        return JsonResponse({'code': 0})


"""
需求:     发送了激活邮件之后,用户登录邮箱.点击链接激活邮箱

前端:     通过js获取点击链接的token.然后把token发送给后端

后端:     请求:
                PUT         emails/verification/?token=xxxxx
         业务逻辑:
                1. 接收请求      token
                2. 提取参数      token
                3. 验证参数      token
                4. 对token进行解密操作
                5. 根据解密的数据查询用户信息
                6. 改变邮箱激活状态
                7. 返回响应
         响应
                code:0
"""


class EmailVerificationView(View):
    def put(self, request):
        # TODO 1. 接收请求      token
        # TODO 2. 提取参数      token
        token = request.GET.get('token')
        # TODO 3. 验证参数      token
        if token is None:
            return JsonResponse({'code': 400, 'errmsg': '没有token信息'})
        # TODO 4. 对token进行解密操作
        from apps.users.common import check_email_token
        data = check_email_token(token)
        if data is None:
            return JsonResponse({'code': 400, 'errmsg': 'token过期或者损坏'})
        # TODO 5. 根据解密的数据查询用户信息
        email = data.get('email')
        user_id = data.get('user_id')
        try:
            user = User.objects.get(email=email, id=user_id)
        except User.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '没有该用户'})
        # TODO 6. 改变邮箱激活状态
        user.email_active = True
        user.save()
        # TODO 7. 返回响应
        return JsonResponse({'code': 0, 'errmsg': 'ok'})


"""
增删改查

web 
    请求 
        业务逻辑[增删改查] 
    响应


增
    1. 接收参数
    2. 提取参数
    3. 验证参数
    4. 数据入库
    5. 返回响应   
删
    1. 获取要删除数据id  指定删除某一[n]条数据   [id指定哪些数据]
    2. 根据id查询数据
    3. 删除数据[物理删除/逻辑删除]
    4. 返回响应
改
    1. 获取要更新[修改]数据id  指定修改某一[n]条数据   [id指定哪些数据]
    2. 根据id查询数据
    3. 接收参数
    4. 提取参数
    5. 验证参数
    6. 数据更新
    7. 返回响应   
查
    1.根据需求查询数据
    2.将对象转换为字典数据
    3.返回响应
"""
from apps.users.models import Address
from utils.user import LoginRequiredJSONMixin


class CreateAddressView(LoginRequiredJSONMixin, View):

    def post(self, request):
        # TODO 新增前 判断用户的地址数量,如果大于等于20个 就不让新增了
        count = Address.objects.filter(user=request.user, is_deleted=False).count()
        if count >= 20:
            return JsonResponse({'code': 0, 'errmsg': '新增地址超过上限'})

        # TODO 1. 接受参数
        json_dict = json.loads(request.body.decode())

        # TODO 2. 提取参数
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # TODO 3. 验证参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return JsonResponse({'code': 400, 'errmsg': '参数缺少'})

        # TODO 4. 数据入库
        # 方式1
        # address = Address(
        #     receiver=receiver,
        #     province_id=province_id,
        #     city_id=city_id,
        #     district_id=district_id,
        #     place=place,
        #     mobile=mobile,
        #     tel=tel,
        #     email=email
        # )
        # address.save()

        # 方式2
        address = Address.objects.create(
            user=request.user,
            title=receiver,
            receiver=receiver,
            province_id=province_id,
            city_id=city_id,
            district_id=district_id,
            place=place,
            mobile=mobile,
            tel=tel,
            email=email
        )

        # TODO 新增地址成功之后,可以判断 用户是否有默认地址
        # TODO 如果没有可以设置一个.就设置新增的这个为默认地址
        if request.user.default_address is None:
            request.user.default_address = address
            request.user.save()

        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # TODO 5.返回响应
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'address': address_dict})


class AddressListView(LoginRequiredJSONMixin, View):

    def get(self, request):
        # TODO 1.根据需求查询数据
        addresses = Address.objects.filter(user=request.user, is_deleted=False)
        # TODO 2.将对象转换为字典数据
        addresses_list = []
        for address in addresses:
            addresses_list.append({
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            })
        # TODO 3.返回响应
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'addresses': addresses_list,
                             'default_address_id': request.user.default_address_id})


"""
http://doc.redisfans.com/

需求:     添加浏览记录  -- redis    list

前端:     登录用户,访问某一个SKU的时候,发送一个访问请求.携带 商品id

后端:     请求:
                        POST        browse_histories    sku_id 在body中
            业务逻辑:
                    1. 接收请求
                    2. 提取参数
                    3. 验证参数
                    4. 数据入库
                        4.1 先去重
                        4.2 再添加
                        4.3 保证列表中有5条浏览记录
                    5. 返回响应

        响应:
                code:0
"""

from apps.goods.models import SKU
from django_redis import get_redis_connection


class UserHistoryView(LoginRequiredJSONMixin, View):

    def post(self, request):
        user = request.user

        # 1. 接收请求
        data = json.loads(request.body.decode())
        # 2. 提取参数
        sku_id = data.get('sku_id')
        # 3. 验证参数
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '没有此商品'})
        # 4. 数据入库
        redis_cli = get_redis_connection('history')
        #     4.1 先去重
        """
        LREM key count value

        根据参数 count 的值，移除列表中与参数 value 相等的元素。

        count 的值可以是以下几种：

        count > 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count 。
        count < 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值。
        count = 0 : 移除表中所有与 value 相等的值。
        """
        redis_cli.lrem(user.id, 0, sku_id)
        #     4.2 再添加
        redis_cli.lpush(user.id, sku_id)
        #     4.3 保证列表中有5条浏览记录
        """
            LTRIM key start stop

        对一个列表进行修剪(trim)，就是说，让列表只保留指定区间内的元素，不在指定区间之内的元素都将被删除。

        举个例子，执行命令 LTRIM list 0 2 ，表示只保留列表 list 的前三个元素，其余元素全部删除
        """
        redis_cli.ltrim(user.id, 0, 4)
        # 5. 返回响应
        return JsonResponse({'code': 0})

    def get(self, request):
        user = request.user
        # TODO 1.根据需求查询数据  list        [1,32,3,4]
        redis_cli = get_redis_connection('history')
        ids = redis_cli.lrange(user.id, 0, -1)

        data_list = []
        for id in ids:
            # TODO 2.根据id查询商品信息
            sku = SKU.objects.get(id=id)
            # TODO 3.将商品信息对象转换为字典数据
            data_list.append({
                'id': sku.id,
                'name': sku.name,
                'price': sku.price,
                'default_image_url': sku.default_image.url  # 调用url 让存储类 拼接 七牛云外链
            })
            # TODO 4. 返回响应
            return JsonResponse({
                'code': 0,
                'errmsg': 'ok',
                'skus': data_list
            })


"""
需求:
        展示用户浏览记录

前端:
        发送请求.携带 cookie中的  sessionid

后端:

    请求:
            GET 
    业务逻辑:
        1.根据需求查询数据  list        [1,32,3,4]
        2.根据id查询商品信息
        3.将商品信息对象转换为字典数据
        4. 返回响应
    响应:
        "code":"0",
        "errmsg":"OK",
        "skus":[
            {
                "id":6,
                "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机",
                "default_image_url":"http://ip/CtM3BVrRbI2ARekNAAFZsBqChgk3141998.png",
                "price":"7988.00"
            },
            ......
        ]
"""
